package com.atguigu.order.service.impl;


import com.alibaba.nacos.common.utils.CollectionUtils;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.common.exception.GmallException;
import com.atguigu.gmall.oms.entity.OrderEntity;
import com.atguigu.gmall.oms.vo.OrderItemVo;
import com.atguigu.gmall.oms.vo.OrderSubmitVo;
import com.atguigu.gmall.pms.entity.SkuEntity;
import com.atguigu.gmall.wms.vo.SkuLockVo;
import com.atguigu.order.feign.OmsClient;
import com.atguigu.order.feign.PmsClient;
import com.atguigu.order.feign.WmsClient;
import com.atguigu.order.service.OrderService;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class OrderServiceImpl implements OrderService {

    String orderTokenPrefix = "order:token:";

    @Autowired
    private PmsClient pmsClient;

    @Autowired
    private WmsClient wmsClient;

    @Autowired
    private OmsClient omsClient;

    @Autowired
    private RedisTemplate redisTemplate;

    Gson gson = new Gson();

    /**
     * 结算页提交订单的方法
     *
     *
     * 异步执行任务的方式：
     * 1.子线程：
     *          线程池、异步编排、springTask
     * 2.mq消息队列：
     *          异步解耦合，提高并发能力
     * 使用mq消息队列的优点：不需要关心异步执行的结果、并且不希望耦合去调用其他服务接口，方便以后的扩展 使用消息队列
     */
    @Override
    public void createOrder(OrderSubmitVo submitVo) {
        //1.防重，解决幂等性问题，使用orderToken判断处理
        //2.验价，使用前端提供的订单项，根据订单项的skuId查询并计算订单总价,再和前端传过来的总价对比，一致则无问题
        //3.验库存锁库存：验证库存是否足够，如果够，锁定库存（需要具有原子性：分布式锁）
        //4.创建订单：
        //5.删除购物车：只删除选中的购物项目

        //1.防重
        //使用token到redis中查询缓存，如果有则删除，代表是第一次提交订单，如果没有或者删除失败，表示不是第一次提交订单
        String orderToken = submitVo.getOrderToken();
        //我们希望在查询和删除redis中token时候具有原子性，我们借助LUA脚本实现
        String script = "if redis.call(’ get’ ,KEYS[1])=ARGV[1] " +
                "then  return redis.call(‘del’ , KEYS[1]) " +
                "else return 0 end";
        Boolean flag = (Boolean) redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class),//todo 该行代码有问题，回头测试
                Arrays.asList(orderTokenPrefix + orderToken), orderToken);
        if (!flag) {//订单重复提交
            throw new GmallException("您的订单已经提交过，请勿重复提交");
        }

        //2.验价
        List<OrderItemVo> items = submitVo.getItems();
        BigDecimal totalPrice = submitVo.getTotalPrice();
        if (CollectionUtils.isEmpty(items)) {
            throw new GmallException("没选中要购买的商品");//前端没有提交订单项
        }
        BigDecimal currentTotalPrice = items.stream().map(orderItemVo -> {
            String skuId = orderItemVo.getSkuId();
            //根据skuId查询数据库拿到sku
            ResponseVo<SkuEntity> skuEntityResponseVo = pmsClient.querySkuById(Long.parseLong(skuId));
            SkuEntity skuEntity = skuEntityResponseVo.getData();
            BigDecimal singlePrice = skuEntity.getPrice();
            return singlePrice.multiply(new BigDecimal(orderItemVo.getCount()));
        }).reduce((a, b) -> a.add(b)).get();
        if (currentTotalPrice.compareTo(totalPrice) != 0) {//前端传过来的价格和我们计算出来的价格不一致
            throw new GmallException("页面数据已经过期，请刷新重试");
        }

        //3.验库存锁库存：验证库存是否足够，如果够，锁定库存（需要具有原子性：分布式锁），锁定库存的信息我们要存到redis里面
        //代码分析，每个订单项都要进行锁库存的操作，如果挨个操作，多次请求sms微服务，浪费，我们干脆用一个list<SkuLockVo>将所有的要锁库存的订单项包装起来，统一操作
        //业务分析,将List<SkuLockVo>填好订单项数据传过去，然后再让sms服务传回来验库存失败的集合List<SkuLockVo>，如果为空，说明说有商品锁库存成功，否则库存不足
        List<SkuLockVo> skuLockVos = items.stream().map(orderItemVo -> {
            SkuLockVo skuLockVo = new SkuLockVo();
            skuLockVo.setSkuId(Long.parseLong(orderItemVo.getSkuId()));
            skuLockVo.setCount(orderItemVo.getCount());
            return skuLockVo;
        }).collect(Collectors.toList());
        ResponseVo<List<SkuLockVo>> wmsResponseVo = wmsClient.checkAndStock(skuLockVos);
        List<SkuLockVo> failSkuLockVos = wmsResponseVo.getData();
        if (failSkuLockVos != null) {//说明库存不足
            throw new GmallException("手慢了，库存不足" + gson.toJson(failSkuLockVos));//将库存不足的商品以json格式返回给前端，不写具体接口了，就这样吧
        }

        //4.创建订单：调用oms-service服务，持久化订单信息。如果创建订单失败，我们直接解锁库存
        //oms接口分析，传参submitVo+userId；响应：返回成功创建的订单对象
        //todo 因为我们还没写单点登录，用户id暂时拿不到 Long userId = LoginInterceptor.getUserInfo().getUserId；
        Long userId = 122L;
        try {
            ResponseVo<OrderEntity> orderResponseVo = omsClient.saveOrder(submitVo, userId);//先编一个userId吧
            OrderEntity orderEntity = orderResponseVo.getData();
        } catch (Exception e) {
            //解锁库存,等会写
            //异步执行释放库存操作
            //消息队列发送释放库存的消息： order服务生产者
            // wms服务监听释放库存的消息：处理业务 消费者


            throw new GmallException("订单创建失败");
        }


        //5.删除购物车：只删除选中的购物项目，异步执行
        //查找购物车需要使用userId,购物车中skuId和CartItem形成映射，根据skuId可以确定购物车中的唯一购物项
        //order服务订单创建成功后发送 删除购物车消息 生产者
        //cart服务监听删除购物项的消息 执行删除任务 消费者
        HashMap map = new HashMap<String,Object>();
        map.put("userId",userId);
        map.put("skuIds",skuLockVos.stream().map(SkuLockVo::getSkuId).collect(Collectors.toList()));
        //rabbitTemplate.convertAndSend("order_exchange","cart.delete.checked",map) todo


    }
}
